﻿using System;
using System.Collections.Generic;
using ParserGenerator.Extensions;
using ParserGenerator.GrammarConstruction;
using ParserGenerator.Output;

namespace ParserGenerator.ParserConstruction
{
    [Serializable]
    public sealed class ParserAutomatonHtmlWriter
    {
        public IndentedWriter Writer { get; }

        public ParserAutomatonHtmlWriter(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Writer = writer;
        }

        private void WriteHtmlHeader()
        {
            Writer.Write("<!DOCTYPE html>\n");
            Writer.Write("<html>\n");
            Writer.OpenBlock();
            Writer.Write("<head>\n");
            Writer.OpenBlock();
            Writer.Write("<meta charset=\"utf-8\">\n");
            Writer.Write("<style>\n");
            Writer.OpenBlock();
            Writer.Write("a:visited { color: darkblue; }\n");
            Writer.Write("a:link    { color: darkblue; text-decoration: none; }\n");
            Writer.Write("a:hover   { color: red; font-weight: bold; }\n");
            Writer.Write("\n");
            Writer.Write("table             { border-spacing: 0; }\n");
            Writer.Write("td                { border-right: 1px solid black; border-bottom: 1px solid black; padding: 5px; }\n");
            Writer.Write("td:first-child    { border-left: 1px solid black; }\n");
            Writer.Write("tr:first-child td { border-top: 1px solid black; }\n");
            Writer.CloseBlock();
            Writer.Write("</style>\n");
            Writer.CloseBlock();
            Writer.Write("</head>\n");
        }

        private void WriteHtmlBody(ParserAutomaton automaton)
        {
            if (automaton == null)
                throw new ArgumentNullException(nameof(automaton));
            Writer.Write("<body>\n");
            Writer.OpenBlock();
            for (int i = 0; i < automaton.States.Count; i++)
            {
                if (i > 0)
                    Writer.Write("\n");
                WriteParserAutomatonState(automaton.States[i]);
            }
            Writer.CloseBlock();
            Writer.Write("</body>\n");
            Writer.CloseBlock();
            Writer.Write("<html>");
        }

        private void WriteParserAutomatonState(ParserAutomatonState state)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            Writer.Write("<p><a name=\"state{0}\"><b>{0}</b></a>", state);
            if (!state.IsConsistent)
                Writer.Write(" *");
            Writer.Write(" : {0}", state.Symbol.Name.AsHtmlText());
            Writer.Write("</p>\n");
            Writer.Write("<table>\n");
            Writer.OpenBlock();
            HashSet<GrammarTerminalSymbol> conflictedTokens = state.GetConflictedTokens();
            WriteItemSet(state, state.Core, conflictedTokens);
            ParserAutomatonItemSet closure = state.Core.ComputeClosure();
            WriteItemSet(state, closure, conflictedTokens, "lightgray");
            Writer.CloseBlock();
            Writer.Write("</table>\n");
            if (state.Transitions.Count > 0)
            {
                Writer.Write("<p>transitions:</p>\n");
                Writer.OpenBlock();
                foreach (ParserAutomatonTransition tran in state.Transitions.Values)
                {
                    Writer.Write("<p>&nbsp;&nbsp;{0}: ", tran.Symbol.Name.AsHtmlText());
                    Writer.Write(tran.IsShift ? "shift to" : "goto");
                    Writer.Write(" <a href=\"#state{0}\">{0}</a>", tran.Target.Number);
                    Writer.Write("</p>\n");
                }
                Writer.CloseBlock();
            }
            if (state.Reductions.Count > 0)
            {
                Writer.Write("<p>reductions:</p>\n");
                Writer.OpenBlock();
                foreach (ParserAutomatonReduction reduce in state.Reductions.Values)
                {
                    Writer.Write("<p>&nbsp;&nbsp;");
                    WriteProduction(reduce.Production);
                    if (!state.IsConsistent)
                    {
                        Writer.Write(" . ");
                        WriteLookaheadSet(reduce.LookaheadSet, conflictedTokens);
                    }
                    Writer.Write("</p>\n");
                }
                Writer.CloseBlock();
            }
            if (state.Predecessors.Count > 0)
            {
                Writer.Write("<p>predecessors:</p>\n");
                Writer.Write("<p>\n");
                Writer.OpenBlock();
                foreach (ParserAutomatonState pred in state.Predecessors)
                    Writer.Write("&nbsp;&nbsp;<a href=\"#state{0}\">{0}</a>\n", pred.Number);
                Writer.CloseBlock();
                Writer.Write("</p>\n");
            }
            if (state.InadequacyNodes.Count > 0)
            {
                foreach (ParserAutomatonInadequacyNode node in state.InadequacyNodes)
                {
                    Writer.Write("<p>{0}", node.Action.Shift != null ? "shift" : "reduce");
                    Writer.Write("-reduce conflict on {0}:</p>\n", node.Action.Token.Name.AsHtmlText());
                    Writer.OpenBlock();
                    foreach (ParserAutomatonContribution c in node.Action)
                    {
                        if (c.IsReduction)
                        {
                            Writer.Write("<p>&nbsp;&nbsp;reduce: ");
                            WriteItem(c.AsReduction.Item);
                            Writer.Write(" {");
                            WriteLookaheadSymbol(node.Action.Token, "red");
                            Writer.Write("}</p>\n");
                        }
                        else
                        {
                            foreach (ParserAutomatonItem item in c.AsShift.GetRelatedItems())
                            {
                                Writer.Write("<p>&nbsp;&nbsp;&nbsp;shift: ");
                                WriteItem(item);
                                Writer.Write("</p>\n");
                            }
                        }
                    }
                    Writer.CloseBlock();
                }
            }
        }

        private void WriteProduction(GrammarProduction production)
        {
            if (production == null)
                throw new ArgumentNullException(nameof(production));
            Writer.Write(production.Owner.Name.AsHtmlText());
            Writer.Write(" &rarr;");
            foreach (GrammarSymbol sym in production.Symbols)
                Writer.Write(" {0}", sym.Name.AsHtmlText());
        }

        private void WriteItem(ParserAutomatonItem item)
        {
            if (item == null)
                throw new ArgumentNullException(nameof(item));
            Writer.Write(item.Production.Owner.Name.AsHtmlText());
            Writer.Write(" &rarr;");
            for (int i = 0; i <= item.Production.Length; i++)
            {
                if (i == item.DotPosition)
                    Writer.Write(" .");
                if (i < item.Production.Length)
                    Writer.Write(" {0}", item.Production.Symbols[i].Name.AsHtmlText());
            }
        }

        private void WriteLookaheadSet(ParserAutomatonReductionLookaheadSet lookaheadSet, HashSet<GrammarTerminalSymbol> conflictedTokens)
        {
            if (lookaheadSet == null)
                throw new ArgumentNullException(nameof(lookaheadSet));
            if (conflictedTokens == null)
                throw new ArgumentNullException(nameof(conflictedTokens));
            Writer.Write(" {");
            int k = 0;
            foreach (GrammarTerminalSymbol sym in lookaheadSet)
            {
                if (k++ > 0)
                    Writer.Write(", ");
                string color = conflictedTokens.Contains(sym) ? "red" : "blue";
                WriteLookaheadSymbol(sym, color);
            }
            Writer.Write("}");
        }

        private void WriteLookaheadSymbol(GrammarTerminalSymbol token, string color)
        {
            if (token == null)
                throw new ArgumentNullException(nameof(token));
            if (color == null)
                throw new ArgumentNullException(nameof(color));
            Writer.Write("<font color=\"{0}\">", color);
            Writer.Write(token.Name.AsHtmlText());
            Writer.Write("</font>");
        }

        private void WriteItemSet(ParserAutomatonState state, ParserAutomatonItemSet items, HashSet<GrammarTerminalSymbol> conflictedTokens)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            if (conflictedTokens == null)
                throw new ArgumentNullException(nameof(conflictedTokens));
            WriteItemSet(state, items, conflictedTokens, null);
        }

        private void WriteItemSet(ParserAutomatonState state, ParserAutomatonItemSet items, HashSet<GrammarTerminalSymbol> conflictedTokens, string color)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            if (conflictedTokens == null)
                throw new ArgumentNullException(nameof(conflictedTokens));
            foreach (ParserAutomatonItem item in items)
            {
                Writer.Write("<tr><td");
                if (color != null)
                    Writer.Write(" bgcolor=\"{0}\"", color);
                Writer.Write(">");
                WriteItem(item);
                if (!state.IsConsistent && item.IsReduce)
                {
                    ParserAutomatonReduction reduce = state.GetReduction(item.Production);
                    ParserAutomatonReductionLookaheadSet lookaheadSet = reduce.LookaheadSet;
                    WriteLookaheadSet(lookaheadSet, conflictedTokens);
                }
                Writer.Write("</td></tr>\n");
            }
        }

        public void WriteParserAutomaton(ParserAutomaton automaton)
        {
            if (automaton == null)
                throw new ArgumentNullException(nameof(automaton));
            WriteHtmlHeader();
            WriteHtmlBody(automaton);
        }
    }
}
